#include "NetcdfDimensionWrapper.h"
#include "NetcdfDataReader.h"

namespace OCPP {
	void NetcdfDimensionWrapper::read(GridVariablePtr& variable, const VolumeOptions& options)
	{
		const auto& dimensions = variable->getDims();
		for (auto i = 0; i < dimensions.size(); i++)
		{
			const auto& name = dimensions.at(i);
			auto dimensionVar = variable->findDimVariable(name);

			if (NcUtil::isDimT(name, options))
			{
				dimT = std::make_shared<NetcdfDimensionData>(i);
				dimT->read(dimensionVar, options);
			}
			else if (NcUtil::isDimZ(name, options))
			{
				dimZ = std::make_shared<NetcdfDimensionData>(i);
				dimZ->read(dimensionVar, options);
				if (!options.knownLevelUnits.empty())
				{
					dimZ->units = options.knownLevelUnits;
				}
				extent.dimZ = dimZ->size;
				levelData = dimZ->data;

				auto knownMaxLevelIndex = options.knownMaxLevelIndex;
				if (knownMaxLevelIndex.has_value())
				{
					extent.dimZ = knownMaxLevelIndex.value();
				}

				levelData.resize(extent.dimZ);
			}
			else if (NcUtil::isDimY(name, options))
			{
				auto dimY = std::make_shared<NetcdfDimensionData>(i);
				dimY->read(dimensionVar, name, options);
				extent.dimY = dimY->size;

				if (options.customFindLonLat && !StringUtil::startsWith(name, "lat"))
				{
					auto var = variable->findDimVariable("lat");
					if (var == nullptr) var = variable->findDimVariable("lattitude");
					if (var != nullptr)
					{
						auto dims = dimensionVar->getDims();
						if (dims.size() > 0 && dims[0] == name)
						{
							dimensionVar = var;
							dimY->read(dimensionVar, name, options);
						}
					}
				}

				extent.rectangle.south = dimY->minValue;
				extent.rectangle.north = dimY->maxValue;

				//if (!options.knownNotLonLat)
				{
					extent.rectangle.south = Cesium::CesiumMath::toRadians(extent.rectangle.south);
					extent.rectangle.north = Cesium::CesiumMath::toRadians(extent.rectangle.north);
				}

				this->dimY = dimY;
			}
			else if (NcUtil::isDimX(name, options))
			{
				dimX = std::make_shared<NetcdfDimensionData>(i);
				dimX->read(dimensionVar, options);
				extent.dimX = dimX->size;

				if (options.customFindLonLat && !StringUtil::startsWith(name, "lon"))
				{
					auto var = variable->findDimVariable("lon");
					if (var == nullptr) var = variable->findDimVariable("longitude");
					if (var != nullptr)
					{
						auto dims = dimensionVar->getDims();
						if (dims.size() > 0 && dims[0] == name)
						{
							dimensionVar = var;
							dimX->read(dimensionVar, name, options);
						}
					}
				}

				extent.rectangle.west = dimX->minValue;
				extent.rectangle.east = dimX->maxValue;

				//if (!options.knownNotLonLat)
				{
					extent.rectangle.west = Cesium::CesiumMath::toRadians(extent.rectangle.west);
					extent.rectangle.east = Cesium::CesiumMath::toRadians(extent.rectangle.east);
				}

				if (extent.rectangle.north - extent.rectangle.south == Cesium::CesiumMath::PI) {
					extent.rectangle.north = Cesium::CesiumMath::PI_OVER_TWO;
					extent.rectangle.south = -Cesium::CesiumMath::PI_OVER_TWO;
				}
			}
		}
	}

	String NetcdfDimensionWrapper::getTimeString(int timeIndex)
	{
		const auto& variable = dimT->_variable;
		return variable->getTimeString(timeIndex);
	}

	GridData NetcdfDimensionWrapper::getShapeData(GridVariablePtr& variable, const VolumeOptions& options)
	{
		GridData gridData;
		gridData.varName = variable->getName();
		gridData.levelData = levelData;
		if (dimZ != nullptr)
		{
			gridData.levelUinits = dimZ->units;
		}
		gridData.extent = extent;
		std::vector<size_t> varShape = variable->getShape();


		std::vector<size_t> readOrigin;
		readOrigin.resize(varShape.size());
		std::vector<size_t> readShape = varShape;

		for (auto i = 0; i < readOrigin.size(); i++)
		{
			if (dimT != nullptr && i == dimT->dimensionIndex)
			{
				readShape[i] = 1;
			}
			else
			{
				readOrigin[i] = 0;
				readShape[i] = varShape[i];

				if (i == dimZ->dimensionIndex)
				{
					if (options.knownMaxLevelIndex.has_value())
					{
						readShape[i] = options.knownMaxLevelIndex.value();
					}

					if (options.knownMinLevelIndex.has_value())
					{
						readOrigin[i] = options.knownMinLevelIndex.value();
						readShape[i] -= options.knownMinLevelIndex.value();
					}
				}
			}
		}

		auto ptr = variable->_gridFileReaderPtr.lock();
		gridData.dataReader = ptr->getDataReader(readOrigin, readShape, variable);

		return gridData;
	}

	GridData NetcdfDimensionWrapper::getShapeDataByT(int timeIndex, GridVariablePtr& variable,const VolumeOptions& options)
	{
		GridData gridData;
		gridData.time = getTimeString(timeIndex);
		gridData.varName = variable->getName();
		gridData.levelData = levelData;
		if (dimZ != nullptr)
		{
			gridData.levelUinits = dimZ->units;
		}
		gridData.extent = extent;
		std::vector<size_t> varShape = variable->getShape();


		std::vector<size_t> readOrigin;
		readOrigin.resize(varShape.size());
		std::vector<size_t> readShape = varShape;

		for (auto i = 0; i < readOrigin.size(); i++)
		{
			if (dimT != nullptr && i == dimT->dimensionIndex)
			{
				readOrigin[i] = timeIndex;
				readShape[i] = 1;
			}
			else
			{
				readOrigin[i] = 0;
				readShape[i] = varShape[i];

				if (i == dimZ->dimensionIndex)
				{
					if (options.knownMaxLevelIndex.has_value())
					{
						readShape[i] = options.knownMaxLevelIndex.value();
					}

					if (options.knownMinLevelIndex.has_value())
					{
						readOrigin[i] = options.knownMinLevelIndex.value();
						readShape[i] -= options.knownMinLevelIndex.value();
					}
				}
			}
		}

		auto ptr = variable->_gridFileReaderPtr.lock();
		gridData.dataReader = ptr->getDataReader(readOrigin, readShape, variable);

		return gridData;
	}

	GridData NetcdfDimensionWrapper::getShapeDataByZ(int zIndex, GridVariablePtr& variable, const VolumeOptions& options)
	{
		GridData gridData;
		gridData.varName = variable->getName();
		gridData.level = dimZ->getLevelString(zIndex);
		gridData.levelUinits = dimZ->units;
		gridData.extent = extent;
		gridData.extent.dimZ = 1;
		std::vector<size_t> varShape = variable->getShape();

		std::vector<size_t> readOrigin;
		readOrigin.resize(varShape.size());
		std::vector<size_t> readShape = varShape;

		for (auto i = 0; i < readOrigin.size(); i++)
		{
			if (i == dimZ->dimensionIndex)
			{
				readOrigin[i] = zIndex;
				readShape[i] = 1;
			}
			else
			{
				readOrigin[i] = 0;
				readShape[i] = varShape[i];
			}
		}

		auto ptr = variable->_gridFileReaderPtr.lock();
		gridData.dataReader = ptr->getDataReader(readOrigin, readShape, variable);

		return gridData;
	}

	GridData NetcdfDimensionWrapper::getShapeDataByTZ(int timeIndex, int zIndex, GridVariablePtr& variable, const VolumeOptions& options)
	{
		GridData gridData;
		gridData.time = getTimeString(timeIndex);
		gridData.varName = variable->getName();
		gridData.level = dimZ->getLevelString(zIndex);
		gridData.levelUinits = dimZ->units;
		gridData.extent = extent;
		gridData.extent.dimZ = 1;
		std::vector<size_t> varShape = variable->getShape();

		std::vector<size_t> readOrigin;
		readOrigin.resize(varShape.size());
		std::vector<size_t> readShape = varShape;

		for (auto i = 0; i < readOrigin.size(); i++)
		{
			if (i == dimT->dimensionIndex)
			{
				readOrigin[i] = timeIndex;
				readShape[i] = 1;
			}
			else if (i == dimZ->dimensionIndex)
			{
				readOrigin[i] = zIndex;
				readShape[i] = 1;
			}
			else
			{
				readOrigin[i] = 0;
				readShape[i] = varShape[i];
			}
		}

		auto ptr = variable->_gridFileReaderPtr.lock();
		gridData.dataReader = ptr->getDataReader(readOrigin, readShape, variable);

		return gridData;
	}
}